
`include "common_header.verilog"

//  *************************************************************************
//  File : tx_axi4s_ff.v
//  *************************************************************************
//  This program is controlled by a written license agreement.
//  Unauthorized reproduction or use is expressly prohibited.
//  Copyright (c) 2017 MorethanIP
//  Muenchner Strasse 199, 85757 Karlsfeld, Germany
//  info@morethanip.com
//  http://www.morethanip.com
//  *************************************************************************
//  Designed by : Geoffrey Chacon
//  info@morethanip.com
//  *************************************************************************
//  Description : Transmit AXI4 to FIFO interface converter.
//                This is a parameterizable width interface to the TX FIFO
//                Does not support null or position bytes in the AXI bus.
//                All data must be left aligned and only the last word (TLAST) may
//                contain null bytes
//  Version     : $Id: tx_axi4s_ff.v,v 1.12 2018/10/31 09:27:09 gc Exp $
//  *************************************************************************

module tx_axi4s_ff #(
        parameter FLOP_INPUTS     = 1,                   // Add input registers
        parameter FLOP_OUTPUTS    = 1,                   // Add input registers
        parameter DATA_WIDTH      = 256,                 // TDATA width in bits (multiples of 8)
        parameter DATA_MOD        = Clogb2(DATA_WIDTH/8),// Set to log2(DATA_WIDTH/8)
        parameter KEEP_WIDTH      = DATA_WIDTH/8,        // TKEEP width. 1 bit per byte
        parameter USER_WIDTH      = 30,                  // TUSER width, application specific
        parameter CFG_WRITE64_ENA = 0                    // Enables support for cfg_write64. When set to 0, cfg_write64 must be tied to 0
) (
        input                    reset,       // Active High Reset
        input                    clk,         // Application clock
        input                    cfg_write64, // Configures the FIFO to use only the lower 64-bit of the data bus. Only for FIFOs of 128/256-bits. Drive to 0 if unused
        input                    sw_reset,    // FIFO sync reset (clk)

        /// AXI4 interface
        input                    tvalid,        // tVALID -> valid transaction
        input                    tlast,         // tVALID -> last transaction for a frame
        input   [KEEP_WIDTH-1:0] tkeep,         // tKEEP  -> valid byte qualifier, 1 bit per byte in tdata
        input   [DATA_WIDTH-1:0] tdata,         // tDATA  -> frame data, left aligned
        input   [USER_WIDTH-1:0] tuser,         // tUSER  -> sideband data. 
        output                   tready,        // tREADY -> interface backpressure

        /// FIFO interface
        output                   ff_tx_wren,       //  Write Enable from application
        output                   ff_tx_sop,        //  Start of Packet
        output                   ff_tx_eop,        //  End of Packet
        output  [DATA_MOD-1:0]   ff_tx_emod,       //  Last Word Modulo
        output  [USER_WIDTH-1:0] ff_tx_user,       //  User data, decoded at the higher layer
        output  [DATA_WIDTH-1:0] ff_tx_data,       //  Data Out
        output                   ff_tx_axi_err,    //  Specific AXI error. OR with ff_tx_err outside this module
        input                    ff_tx_rdy         //  almost full, fifo ready
);

// ----------------------------------------------------------------------------------------------------
// Local functions
// ----------------------------------------------------------------------------------------------------
// Constant log2
function integer Clogb2(input integer idepth);
begin : f_clogb2
        integer depth;
        depth = idepth;
        if (depth <= 1)
        begin
                Clogb2 = 1;
        end
        else
        begin
                depth = depth - 1;

                for (Clogb2 = 0; depth > 0; Clogb2 = Clogb2 + 1)
                begin
                        depth = depth >> 1;
                end
        end
end
endfunction

// ----------------------------------------------------------------------------------------------------
// Local parameters
localparam MAX_BYTES   = 16384;
localparam MAX_XACTS   = MAX_BYTES / (DATA_WIDTH/8);
localparam MAX_XACTS64 = MAX_BYTES / (64/8);
localparam XACTS_MOD   = CFG_WRITE64_ENA == 0 ? Clogb2(MAX_XACTS+1) : Clogb2(MAX_XACTS64+1);

localparam KEEP_WIDTH64 = (KEEP_WIDTH>8) ? 8 : KEEP_WIDTH;
localparam DATA_MOD_INT = (DATA_MOD>3) ? DATA_MOD : 3;

// ----------------------------------------------------------------------------------------------------
// Local functions

// Encode tkeep into mod. Look for the last 1 and return the value corresponding to its position
// Returns an error flag and the encoded position as a modulo (if all bits are 1 the returned value is 0)
function automatic [(1+DATA_MOD_INT)-1:0] encode_mod ( input [KEEP_WIDTH-1:0] bit_vector );
        integer k;
        reg     done;
        reg     err;
        integer pos;
        reg [DATA_MOD_INT-1:0] mod;

        begin
                done = ~bit_vector[0];
                err  = ~bit_vector[0]; // Initialize the error flag to 1 if bit [0] is 0
                pos  = 32'd 1; // At least one valid byte is returned

                // Start from bit 1 since bit [0] is taken care above
                for (k = 1; k < KEEP_WIDTH; k = k + 1)
                begin
                        // Set the done flag with the first 0 found
                        if (bit_vector[k] == 1'b 0)
                        begin
                                // Set the done flag with the first ocurring zero
                                done = 1'b 1;
                        end

                        // Encode the position
                        if (done == 1'b 0 && bit_vector[k] == 1'b 1)
                        begin
                                pos = (k+1);
                        end

                        // Set the error flag with any 1 found after done is set
                        // This captures errors due to patterns like 00..1..0011.11
                        //      E: Error bit; i: iterator position       E   i
                        if (done == 1'b 1)
                        begin
                                err = err | bit_vector[k];
                        end
                end

                // Calculate the modulo. Set the modulo to 0 if the final position of the last 1 is equal to the KEEP_WIDTH
                mod = (pos == KEEP_WIDTH) ? {DATA_MOD_INT{1'b 0}} : pos;

                // Return the encoded position up to the last contiguous 1
                encode_mod = {err,mod};
        end
endfunction

// ----------------------------------------------------------------------------------------------------
// Local wires and registers
wire                   cfg_write64_s;      //  synchronized

reg                    sop;          // Whether to insert an SOP in the next transaction
reg                    eop;          // Whether to insert an SOP in the next transaction
reg [XACTS_MOD-1:0]    nxt_xact_ctr; // counts the number of transactions. Inserts a TLAST after 16384 bytes
reg [XACTS_MOD-1:0]    xact_ctr;     // counts the number of transactions. Inserts a TLAST after 16384 bytes
wire                   xact_ctr_max; // When xact_ctr reaches the maximum value
reg                    data_block;        // Blocks data into the FIFO
reg                    nxt_data_block;    // Blocks data into the FIFO

reg                    tvalid_r;
reg                    tlast_r;
reg   [KEEP_WIDTH-1:0] tkeep_r;
wire  [8:0]            tkeep64_r;

reg   [DATA_WIDTH-1:0] tdata_r;
reg   [USER_WIDTH-1:0] tuser_r;
reg   [DATA_MOD_INT-1:0]tmod_r;       // Modulo extracted from tkeep
reg                    tmod_err_r;

reg                    ff_tx_rdy_r;  // Delayed ff_tx_rdy

// ----------------------------------------------------------------------------------------------------
// Input registers
generate
if (FLOP_INPUTS == 1)
begin : g_input_registers
        always @(posedge clk or posedge reset)
        begin : p_in_regs
                if (reset == 1'b 1)
                begin
                        tvalid_r <= 1'b 0;
                        tlast_r  <= 1'b 0;
                        tkeep_r  <= {KEEP_WIDTH{1'b 0}};
                        tdata_r  <= {DATA_WIDTH{1'b 0}};
                        tuser_r  <= {USER_WIDTH{1'b 0}};
                end
                else
                begin
                        if (sw_reset == 1'b 1)
                        begin
                                tvalid_r <= 1'b 0;
                                tlast_r  <= 1'b 0;
                                tkeep_r  <= {KEEP_WIDTH{1'b 0}};
                                tdata_r  <= {DATA_WIDTH{1'b 0}};
                                tuser_r  <= {USER_WIDTH{1'b 0}};
                        end
                        else if (tvalid_r == 1'b 0 || ff_tx_rdy_r == 1'b 1)
                        begin
                                tvalid_r <= tvalid;
                                tlast_r  <= tlast;
                                tkeep_r  <= tkeep;
                                tdata_r  <= tdata;
                                tuser_r  <= tuser;
                        end
                end
        end

        assign tready = ~tvalid_r | ff_tx_rdy_r | data_block;
end

else
begin : g_noinput_registers
        always @(*)
        begin : p_in_comb
                tvalid_r = tvalid;
                tlast_r  = tlast;
                tkeep_r  = tkeep;
                tdata_r  = tdata;
                tuser_r  = tuser;
        end

        assign tready = ff_tx_rdy_r | data_block;
end
endgenerate

mtip_dffvec #(1) U_DFFSYNC (
        .reset  (reset),
        .clk    (clk),
        .i      (cfg_write64),
        .o      (cfg_write64_s)
);


// ----------------------------------------------------------------------------------------------------
// Control logic

// Determine if the frame exceeds 16384 bytes. If so, set nxt_data_block to 1 to truncate the frame
assign xact_ctr_max = (cfg_write64_s == 1'b 0) ? xact_ctr == (MAX_XACTS-1)
                                               : xact_ctr == (MAX_XACTS64-1);

always @(*)
begin : p_data_block
        if (tvalid_r == 1'b 1 && sop == 1'b 1 && data_block == 1'b 0)
        begin
                nxt_data_block = 1'b 0;
                nxt_xact_ctr = 1;
        end
        else if (tvalid_r == 1'b 1 && tlast_r == 1'b 0 && xact_ctr_max == 1'b 0 && data_block == 1'b 0)
        begin
                nxt_data_block = 1'b 0;
                nxt_xact_ctr = xact_ctr + {{XACTS_MOD-1{1'b 0}}, 1'b 1};
        end
        else if (tvalid_r == 1'b 1 && tlast_r == 1'b 0 && xact_ctr_max == 1'b 1 && data_block == 1'b 0)
        begin
                // This one causes the bytes to exceed to 16384
                nxt_data_block = 1'b 1;
                nxt_xact_ctr = xact_ctr + {{XACTS_MOD-1{1'b 0}}, 1'b 1};
        end
        else if (tvalid_r == 1'b 1 && tlast_r == 1'b 1)
        begin
                // Clear the data_block if needed to be cleared, and reset the xact counter
                nxt_data_block = 1'b 0;
                nxt_xact_ctr = 0;
        end
        else
        begin
                nxt_data_block = data_block;
                nxt_xact_ctr = xact_ctr;
        end
end


// Generate the sop indication, and check for maximum frame length
always @(posedge clk or posedge reset)
begin : p_sop_gen
        if (reset == 1'b 1)
        begin
                sop <= 1'b 1;
                eop <= 1'b 0;
                xact_ctr <= {XACTS_MOD{1'b 0}};
                data_block <= 1'b 0;
        end
        else
        begin
                if (sw_reset == 1'b 1)
                begin
                        sop <= 1'b 1;
                        eop <= 1'b 0;
                        xact_ctr <= {XACTS_MOD{1'b 0}};
                        data_block <= tvalid_r; // Use tvalid to prevent errors from propagating

                end
                else if (tvalid_r == 1'b 1 && ff_tx_rdy_r == 1'b 1)
                begin
                        data_block <= nxt_data_block;
                        xact_ctr <= nxt_xact_ctr;

                        if (tlast_r == 1'b 1)
                        begin
                                sop <= 1'b 1;
                                eop <= 1'b 0;
                        end
                        else if (nxt_data_block == 1'b 1 && data_block == 1'b 0)
                        begin
                                sop <= 1'b 0;
                                eop <= 1'b 1;
                        end
                        else
                        begin
                                sop <= 1'b 0;
                                eop <= 1'b 0;
                        end
                end
        end
end

// ----------------------------------------------------------------------------------------------------
// Interface adapter

assign tkeep64_r = {{9-KEEP_WIDTH64{1'b 0}}, tkeep_r[KEEP_WIDTH64-1:0]};

// Calculate the modulo for the FIFO, and check for errors in tkeep
always @(*)
begin : p_keep_to_mod
        integer i;

        if (tlast_r == 1'b 0 && eop == 1'b 0)
        begin
                // Non-lsat word, assume full words, but check for tkeep violations
                tmod_r = {DATA_MOD_INT{1'b 0}};
                tmod_err_r = cfg_write64_s == 1'b 0 
                              ? ~&tkeep_r       // Assert error if not all bits of tkeep are set to 1 
                              : ~&tkeep_r[KEEP_WIDTH64-1:0]; // 64-bit write mode
        end
        else
        begin
                // Last word, check how many bytes are valid, and errors in tkeep
                if (tkeep_r[0] == 1'b 0)
                begin
                        // tkeep[0] should always be 1. If it is 0 it is an error condition
                        // so consider the full word to be bad
                        tmod_r   = {DATA_MOD_INT{1'b 0}};
                        tmod_err_r = 1'b 1;
                end
                else if (cfg_write64_s == 1'b 0 && (&tkeep_r == 1'b 1))
                begin
                        // All bits are valid, so this is a full word
                        tmod_r   = {DATA_MOD_INT{1'b 0}};
                        tmod_err_r = 1'b 0;
                end
                else if (cfg_write64_s == 1'b 1 && (&tkeep_r[KEEP_WIDTH64-1:0] == 1'b 1))
                begin
                        // All bits are valid, so this is a full 64-bit word
                        tmod_r   = {DATA_MOD_INT{1'b 0}};
                        tmod_err_r = 1'b 0;
                end
                else
                begin
                        // Not all bits in tkeep are set. Search for the 
                        // last one (first zero) bit from left to right
                        // Check that the remainder of tkeep bits are 0
                        if (cfg_write64_s == 1'b 0)
                        begin
                                // Encode the bit-vector tkeep into a modulo-N integer
                                {tmod_err_r,tmod_r} = encode_mod(tkeep_r);
                        end
                        else 
                        begin
                                // 64-bit mode
                                tmod_r = {DATA_MOD_INT{1'b 0}};
                                tmod_err_r = 1'b 0;
                                case (tkeep64_r[6:0])
                                7'b 000_0001: tmod_r[2:0] = 3'd 1; 
                                7'b 000_0011: tmod_r[2:0] = 3'd 2; 
                                7'b 000_0111: tmod_r[2:0] = 3'd 3; 
                                7'b 000_1111: tmod_r[2:0] = 3'd 4; 
                                7'b 001_1111: tmod_r[2:0] = 3'd 5; 
                                7'b 011_1111: tmod_r[2:0] = 3'd 6; 
                                7'b 111_1111: tmod_r[2:0] = 3'd 7; 
                                default:
                                begin
                                        tmod_r[2:0] = 3'd 1;
                                        tmod_err_r = 1'b 1;
                                end
                                endcase
                        end
                end
        end
end

// For the ready signal, the FIFO can take up to two words after ff_tx_rdy asserts
always @(posedge clk or posedge reset)
begin : p_ff_tx_rdy_r
        if (reset == 1'b 1)
        begin
                ff_tx_rdy_r <= 1'b 0;
        end
        else
        begin
                ff_tx_rdy_r <= ff_tx_rdy;
        end
end

// ----------------------------------------------------------------------------------------------------
// Outputs. May need to be flopped if timing is not good enough
generate
if (FLOP_OUTPUTS == 1'b 1) 
begin : g_output_registers
        /// FIFO interface
        reg                   ff_tx_wren_r;       //  Write Enable from application
        reg                   ff_tx_sop_r;        //  Start of Packet
        reg                   ff_tx_eop_r;        //  End of Packet
        reg  [DATA_MOD-1:0]   ff_tx_emod_r;       //  Last Word Modulo
        reg  [USER_WIDTH-1:0] ff_tx_user_r;       //  User data, decoded at the higher layer
        reg  [DATA_WIDTH-1:0] ff_tx_data_r;       //  Data Out
        reg                   ff_tx_axi_err_r;    //  Specific AXI error. OR with ff_tx_err outside this module

        always @(posedge clk or posedge reset)
        begin : p_out_regs
                if (reset == 1'b 1)
                begin
                        ff_tx_wren_r    <= 1'b 0;
                        ff_tx_sop_r     <= 1'b 0;
                        ff_tx_eop_r     <= 1'b 0;
                        ff_tx_emod_r    <= {DATA_MOD{1'b 0}};
                        ff_tx_user_r    <= {USER_WIDTH{1'b 0}};
                        ff_tx_data_r    <= {DATA_WIDTH{1'b 0}};
                        ff_tx_axi_err_r <= 1'b 0;
                end
                else
                begin
                        if (sw_reset == 1'b 1)
                        begin
                                ff_tx_wren_r    <= 1'b 0;
                                ff_tx_sop_r     <= 1'b 0;
                                ff_tx_eop_r     <= 1'b 0;
                                ff_tx_emod_r    <= {DATA_MOD{1'b 0}};
                                ff_tx_user_r    <= {USER_WIDTH{1'b 0}};
                                ff_tx_data_r    <= {DATA_WIDTH{1'b 0}};
                                ff_tx_axi_err_r <= 1'b 0;
                        end
                        else if (ff_tx_wren_r == 1'b 0 || ff_tx_rdy_r == 1'b 1)
                        begin
                                ff_tx_wren_r <= (tvalid_r & ~data_block) | eop;

                                if (tvalid_r == 1'b 1)
                                begin
                                        // Gate these signals with tvalid_r for fine-grained clock gating
                                        ff_tx_sop_r     <= sop;
                                        ff_tx_eop_r     <= tlast_r | eop;
                                        ff_tx_emod_r    <= tmod_r[DATA_MOD-1:0];
                                        ff_tx_axi_err_r <= tmod_err_r | eop;
                                        ff_tx_data_r    <= tdata_r;
                                        ff_tx_user_r    <= tuser_r;
                                end
                        end
                end
        end

        // Drive the FIFO interface from registers
        assign ff_tx_wren    = ff_tx_wren_r & ff_tx_rdy_r;
        assign ff_tx_sop     = ff_tx_sop_r;
        assign ff_tx_eop     = ff_tx_eop_r;
        assign ff_tx_emod    = ff_tx_emod_r;
        assign ff_tx_axi_err = ff_tx_axi_err_r;
        assign ff_tx_data    = ff_tx_data_r;
        assign ff_tx_user    = ff_tx_user_r;

end

else
begin : g_nooutput_registers

        // Drive the FIFO interface without registers
        assign ff_tx_wren    = ((tvalid_r & ~data_block) | eop) & ff_tx_rdy_r;
        assign ff_tx_sop     = sop;
        assign ff_tx_eop     = tlast_r | eop;
        assign ff_tx_emod    = tmod_r[DATA_MOD-1:0];
        assign ff_tx_axi_err = tmod_err_r | eop;
        assign ff_tx_data    = tdata_r;
        assign ff_tx_user    = tuser_r;

end
endgenerate

// ----------------------------------------------------------------------------------------------------
// Assertion checking

`ifndef SYNTHESIS
// synopsys translate_off
// synthesis translate_off
`ifdef MTIPASSERTIONS
reg msg_blocked;
always @(posedge clk or posedge reset)
begin : p_messages
        // Output an error message if a frame is truncated
        if (reset == 1'b 1)
        begin
                msg_blocked <= 1'b 0;
        end
        else
        begin
                if (tvalid_r == 1'b 1 && data_block == 1'b 1 && msg_blocked == 1'b 0)
                begin
                        $display("%0t [tx_axi4s_ff.v] ERROR - Truncating frame which exceeds 16384 bytes. (%m)", $time());
                        msg_blocked <= 1'b 1;
                end
                else if (tvalid_r == 1'b 1 && data_block == 1'b 0)
                begin
                        msg_blocked <= 1'b 0;
                end
        end
end

`ifdef MTIPASSERTIONS_SV
reg     ff_in_frame;

// Check for SOP and EOP violations at the output interface
always @(posedge clk or posedge reset)
begin : p_sop_eop_violations
        // Output an error message if a frame is truncated
        if (reset == 1'b 1 | sw_reset == 1'b 1)
        begin
                ff_in_frame <= 1'b 0;
        end
        else
        begin
                if (ff_tx_wren == 1'b 1 && ff_tx_sop == 1'b 1)
                begin
                        ff_in_frame <= ~ff_tx_eop;
                        assert (ff_in_frame == 1'b 0)
                           else $display("%0t [tx_axi4s_ff.v] ERROR - FIFO protocol violation, unexpected SOP (%m).", $time());
                end

                if (ff_tx_wren == 1'b 1 && ff_tx_eop == 1'b 1 && ff_tx_sop == 1'b 0)
                begin
                        ff_in_frame <= 1'b 0;
                        assert (ff_in_frame == 1'b 1)
                           else $display("%0t [tx_axi4s_ff.v] ERROR - FIFO protocol violation, unexpected EOP (%m).", $time());
                end
        end
end

// Check that input data remains stable on tready deassertion
ASSUME__STABLE_DATA_ON_TREADY_DEASSERTION:
assume property (@(posedge clk) disable iff (reset)
                 tvalid & ~tready |=> $stable({tvalid, tlast, tkeep, tdata, tuser})) 
                 else $error($sprintf("%0t [tx_axi4s_ff.v] ERROR - AXI input violation. Data not stable while tready is low (%m)", $time));

// Check that TKEEP is all 1's when TLAST is 0
ASSUME__TKEEP_CHK_1A:
assume property (@(posedge clk) disable iff (reset | cfg_write64_s)
                 tvalid & ~tlast |-> (&tkeep == 1'b 1))
                 else $error($sformatf("%0t [tx_axi4s_ff.v] ERROR - Invalid tKEEP value when tLAST is 0.", $time));

ASSUME__TKEEP_CHK_1B:
assume property (@(posedge clk) disable iff (reset | ~cfg_write64_s)
                 tvalid & ~tlast |-> (&tkeep[KEEP_WIDTH64-1:0] == 1'b 1))
                 else $error($sformatf("%0t [tx_axi4s_ff.v] ERROR - Invalid tKEEP value when tLAST is 0 (64-bit write mode).", $time));

// Check that tkeep is valid for the interface (contiguous 1's) when not tlast
ASSUME__TKEEP_CHK_2A:
assume property (@(posedge clk) disable iff (reset | cfg_write64_s)
                 tvalid & tlast |-> ($countones({1'b0,tkeep}+1) == 1))
                 else $error($sformatf("%0t [tx_axi4s_ff.v] ERROR - Invalid tKEEP value for when tLAST asserts.", $time));

ASSUME__TKEEP_CHK_2B:
assume property (@(posedge clk) disable iff (reset | ~cfg_write64_s)
                 tvalid & tlast |-> ($countones({1'b0,tkeep}+1) == 1))
                 else $error($sformatf("%0t [tx_axi4s_ff.v] ERROR - Invalid tKEEP value for when tLAST asserts (64-bit write mode).", $time));

// Check that tkeep is never all zeroes when tvalid asserts
ASSUME__TKEEP_CHK_3A:
assume property (@(posedge clk) disable iff (reset | cfg_write64_s)
                 tvalid |-> (|tkeep == 1'b 1))
                 else $error($sformatf("%0t [tx_axi4s_ff.v] ERROR - Invalid tKEEP value (all-zeroes).", $time));

ASSUME__TKEEP_CHK_3B:
assume property (@(posedge clk) disable iff (reset | cfg_write64_s)
                 tvalid |-> (|tkeep[KEEP_WIDTH64-1:0] == 1'b 1))
                 else $error($sformatf("%0t [tx_axi4s_ff.v] ERROR - Invalid tKEEP value (all-zeroes) (64-bit write mode).", $time));

// Internal checks

// Check that ff_tx_wren deasserts one cycle after ff_tx_rdy deasserts
ASSERT__WREN_DEASSERTS_AFTER_RDY:
assume property (@(posedge clk) disable iff (reset)
                 ~ff_tx_rdy |=> ~ff_tx_wren)
                 else $error($sformatf("%0t [tx_axi4s_ff.v] ERROR - ff_tx_wren did not deassert one cycle after ff_tx_rdy deasserted.", $time));
`endif

`endif
// synthesis translate_on
// synopsys translate_on
`endif


endmodule
